package dmwr.repo.gae;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.logging.Logger;

import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.CompositeFilter;
import com.google.appengine.api.datastore.Query.CompositeFilterOperator;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;

import dmwr.Dmwr;
import dmwr.bean.Application;
import dmwr.bean.Shot;
import dmwr.repo.ShotRepo;

public class ShotRepoGae implements ShotRepo {
	private static final Logger log = Logger.getLogger(ShotRepoGae.class
			.getName());
	private static final String KIND = "Shot";
	private static final String PROPERTY_PLAYER_ID = "playerId";
	private static final String PROPERTY_POOL_ID = "poolId";
	private static final String PROPERTY_ENTRY_ID = "entryId";
	private static final String PROPERTY_POSITION = "position";
	private static final String PROPERTY_TREND = "trend";
	private static final String PROPERTY_CREATED = "created";

	@Override
	public List<Shot> at(Long playerId, Long poolId, Long position) {
		Collection<Filter> subFilters = new ArrayList<Query.Filter>();
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_PLAYER_ID, playerId));
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_POOL_ID, poolId));
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_POSITION, position));
		List<Shot> list = shots(
				new Query(KIND).setFilter(
						new CompositeFilter(CompositeFilterOperator.AND,
								subFilters)).addSort(PROPERTY_CREATED,
						Query.SortDirection.ASCENDING), 1000);
		log.info("shot at " + position + " " + list);
		return list;
	}

	@Override
	public Shot firstAfter(Long playerId, Long poolId, Long position) {
		Collection<Filter> subFilters = new ArrayList<Query.Filter>();
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_PLAYER_ID, playerId));
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_POOL_ID, poolId));
		subFilters.add(FilterOperator.GREATER_THAN.of(PROPERTY_POSITION,
				position));
		Shot item = shot(new Query(KIND).setFilter(
				new CompositeFilter(CompositeFilterOperator.AND, subFilters))
				.addSort(PROPERTY_POSITION, Query.SortDirection.ASCENDING));
		log.info("shot firstAfter " + position + " " + item);
		return item;
	}

	@Override
	public Shot latest(Long playerId, Long poolId) {
		Collection<Filter> subFilters = new ArrayList<Query.Filter>();
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_PLAYER_ID, playerId));
		subFilters.add(FilterOperator.EQUAL.of(PROPERTY_POOL_ID, poolId));
		Shot shot = shot(new Query(KIND).setFilter(
				new CompositeFilter(CompositeFilterOperator.AND, subFilters))
				.addSort(PROPERTY_CREATED, Query.SortDirection.DESCENDING));
		log.info("shot latest " + shot + " (playerId=" + playerId + ",poolId="
				+ poolId + ")");
		return shot;
	}

	@Override
	public void save(Shot shot) {
		log.info("shot save " + shot);
		Entity entity;
		if (shot.getId() == null) {
			entity = new Entity(KIND);
			entity.setProperty(PROPERTY_CREATED, System.currentTimeMillis());
		} else {
			Key key = KeyFactory.createKey(KIND, shot.getId());
			try {
				entity = Gae.datastoreService().get(key);
			} catch (EntityNotFoundException e) {
				throw new RuntimeException(e);
			}
		}
		entity.setProperty(PROPERTY_PLAYER_ID, shot.getPlayerId());
		entity.setProperty(PROPERTY_POOL_ID, shot.getPoolId());
		entity.setProperty(PROPERTY_ENTRY_ID, shot.getEntryId());
		entity.setProperty(PROPERTY_POSITION, shot.getPosition());
		entity.setProperty(PROPERTY_TREND, shot.getTrend());
		Gae.datastoreService().put(entity);
		if (shot.getId() == null) {
			shot.setId(entity.getKey().getId());
		}
	}

	@Override
	public void deleteAll() {
		Query query = new Query(KIND).addSort(PROPERTY_CREATED,
				Query.SortDirection.ASCENDING);
		List<Entity> results;
		while (!(results = Gae.datastoreService().prepare(query)
				.asList(FetchOptions.Builder.withLimit(100))).isEmpty()) {
			for (Entity result : results) {
				Gae.datastoreService().delete(result.getKey());
			}
		}
		Application application = Dmwr.applicationRepo().get();
		application.setEntryCount(0L);
		Dmwr.applicationRepo().save(application);
	}

	private List<Shot> shots(Query query, int limit) {
		List<Entity> results = Gae.datastoreService().prepare(query)
				.asList(FetchOptions.Builder.withLimit(limit));
		List<Shot> list = new ArrayList<Shot>(results.size());
		for (Entity result : results) {
			list.add(shot(result));
		}
		return list;
	}

	private Shot shot(Query query) {
		List<Entity> results = Gae.datastoreService().prepare(query)
				.asList(FetchOptions.Builder.withLimit(1));
		Entity result = !results.isEmpty() ? results.get(0) : null;
		return shot(result);
	}

	private Shot shot(Entity result) {
		Shot item = result != null ? new Shot(result.getKey().getId(),
				(Long) result.getProperty(PROPERTY_PLAYER_ID),
				(Long) result.getProperty(PROPERTY_POOL_ID),
				(Long) result.getProperty(PROPERTY_ENTRY_ID),
				(Long) result.getProperty(PROPERTY_POSITION),
				(Long) result.getProperty(PROPERTY_TREND),
				(Long) result.getProperty(PROPERTY_CREATED)) : null;
		return item;
	}
}
